#pragma once
//#include "oj_Model.hpp"
#include "oj_Model2.hpp"
#include "oj_View.hpp"
#include <mutex>
#include <fstream>
#include <jsoncpp/json/json.h>
#include "../comm/httplib.h"

using namespace oj_model;
using namespace oj_View;
using namespace httplib;
namespace oj_control
{
    class Machine
    {
    public:
        std::string _ip;
        int _port;
        uint64_t _load;
        // 客户端访问与离开对未来++或--_load必须是合法的
        std::mutex *_mutex; // mutex禁止拷贝，所以用指针

    public:
        Machine()
            : _ip(""), _port(0), _load(0), _mutex(nullptr)
        {
        }
        ~Machine()
        {
        }

        void Incress_load()
        {
            if (_mutex)
                _mutex->lock();
            _load++;
            if (_mutex)
                _mutex->unlock();
        }

        void Decress_load()
        {
            if (_mutex)
                _mutex->lock();
            _load--;
            if (_mutex)
                _mutex->unlock();
        }

        uint64_t GetLoad()
        {
            uint64_t load = 0;
            if (_mutex)
                _mutex->lock();
            load = _load;
            if (_mutex)
                _mutex->unlock();
            return load;
        }

        void ResetLoad()
        {
            if (_mutex)
                _mutex->lock();
            _load = 0;
            if (_mutex)
                _mutex->unlock();
        }
    };

    static const std::string machines_path = "./sever_machines/sever_machines_conf";
    class LoadBalance
    {
    public:
        // 管理所有的负载机器
        std::vector<Machine> _machines;
        // 在线机器的id
        std::vector<int> _online;
        // 离线机器的id
        std::vector<int> _offline;
        // 未来多个请求访问LoadBalance模块的三个资源，需要加锁
        std::mutex _m;

    public:
        LoadBalance()
        {
            assert(LoadConf(machines_path));
            LOG(INFO,"加载: %s 成功",machines_path.c_str());
        }
        ~LoadBalance()
        {
        }
        bool LoadConf(const std::string &path)
        {
            std::ifstream in(path);
            if (!in.is_open())
            {
                LOG(ERROR, "打开machines_conf失败,无法添加机器配置");
                return false;
            }

            std::string line;
            while (getline(in, line))
            {
                std::vector<std::string> store_ip_port;
                StringUtil::SplitString(line, &store_ip_port, ":");
                if (store_ip_port.size() != 2)
                {
                    LOG(WARNNING, "获取ip与port不完整");
                    continue;
                }
                Machine m;
                m._ip = store_ip_port[0];
                m._port = atoi(store_ip_port[1].c_str());
                m._load = 0;
                m._mutex = new std::mutex();
                // 把信息加入负载均衡机器
                _online.push_back(_machines.size());
                _machines.push_back(m);

            }
            in.close();
            return true;
        }

        // 都是输出型参数，第一个参数为对应machine的id,第二个参数为想得到对应machine的地址，用指针直接操作
        bool SmartChoice(int *id, Machine **m)
        {
            // 选负载最小的那个机器
            // 得到目前在线机器的负载，选出最小的那个
            _m.lock();
            int online_num = _online.size();
            if (online_num == 0)
            {
                _m.unlock();
                LOG(FATAL, "当前在线主机为 0 台，无法进行服务");
                return false;
            }
            *id = _online[0];
            *m = &_machines[_online[0]];
            uint64_t minload = _machines[_online[0]].GetLoad();
            // 轮询查找
            for (int i = 1; i < online_num; i++)
            {
                if (minload > _machines[_online[i]].GetLoad())
                {
                    minload = _machines[_online[i]].GetLoad();
                    *id = _online[i];
                    *m = &_machines[_online[i]];
                }
            }
            _m.unlock();
            return true;
        }

        void OffLine(int offid)
        {
            _m.lock();
            for (auto iter = _online.begin(); iter != _online.end(); iter++)
            {
                if (*iter == offid)
                {
                    //离线也要记得清空负载，否则下一次轮不到它
                    _machines[offid].ResetLoad();
                    _online.erase(iter);
                    _offline.push_back(offid);//这里不能用*iter,会迭代器失效
                    break;
                }
            }
            _m.unlock();
        }

        void OnLine()
        {
            //以后统一上线
            //TO DO
            _m.lock();
            _online.insert(_online.end(),_offline.begin(),_offline.end());
            //_offline.erase(_offline.begin(),_offline.end());
            _offline.clear();
            _m.unlock();

            LOG(INFO,"所有主机已上线!");
        }

        //for test
        void ShowMachines()
        {
            _m.lock();
            LOG(INFO,"在线主机列表: ");
            for(auto& id : _online)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            LOG(INFO,"离线主机列表: ");
            for(auto& id : _offline)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            _m.unlock();

        }
    };
    // control, 控制器，就是我们的核心业务逻辑,组合Model的后端数据与View的前端网页
    class Control
    {
    private:
        Model _model;
        View _view;
        LoadBalance _load_bal;

    public:
        Control()
        {
        }
        ~Control()
        {
        }
        void Recover()
        {
            _load_bal.OnLine();
        }
        // 获取所有题目 + 构建网页
        bool AllQuestions(std::string *html)
        {
            bool ret = true;
            vector<Question> all;
            if (_model.GetAllQuestions(&all))
            {
                //先按id排序
                sort(all.begin(),all.end(),[](const Question& q1,const Question& q2){
                    return atoi(q1._num.c_str()) < atoi(q2._num.c_str());
                });
                // 拿所有题目构建成网页
                _view.AllExpend(all, html);
            }
            else
            {
                *html = "获取所有题目失败, 形成题目列表失败";
                ret = false;
            }
            return ret;
        }

        // 获取指定题目内容 + 构建网页
        bool OneQuestion(const std::string num, std::string *html)
        {
            bool ret = true;
            Question q;
            if (_model.GetOneQuestion(num, &q))
            {
                // 拿指定的题目构建成网页
                _view.OneExpend(q, html);
            }
            else
            {
                *html = "获取单个题目内容失败,num: " + num + "不存在";
                ret = false;
            }
            return ret;
        }

        void Judge(const std::string& num, const std::string& in_json, std::string *out_json)
        {
            // 0.获取题目的信息
            Question q;
            _model.GetOneQuestion(num, &q);

            // 1.得到用户的in_json,反序列化
            Json::Reader reader;
            Json::Value in_val;
            // 把in_json里的数据填到in_val里
            reader.parse(in_json, in_val);
            std::string code = in_val["code"].asString();

            // 2.拼接用户code 和 服务端测试用例
            Json::Value compile_val;
            compile_val["code"] = code + "\n" + q._tail;
            compile_val["input"] = in_val["input"].asString();
            compile_val["cpu_rlimit"] = q._cpu_rlimit;
            compile_val["mem_rlimit"] = q._mem_rlimit;

            // Json::StreamWriterBuilder writer;
            // writer["emitUTF8"] = true;
            // // 下面要把序列化好的这个串发送
            // std::string compile_str = Json::writeString(writer, compile_val);
            Json::FastWriter writer;
            std::string compile_string = writer.write(compile_val);

            // 3.引入负载均衡模块，选择负载最小的主机
            while (1)
            {
                int id = 0;
                Machine *ma = nullptr;
                if (!_load_bal.SmartChoice(&id, &ma))
                {
                    LOG(INFO,"负载均衡模块失效，主机全部下线，结束服务");
                    break;
                }
                // 4.发送http请求给CR模块,增加负载
                Client client(ma->_ip, ma->_port);
                ma->Incress_load();
                LOG(INFO, "请求主机的id: %d 选择主机成功,ip: %s port: %d,当前主机的负载是: %d", id, ma->_ip.c_str(), ma->_port,ma->_load);
                LOG(INFO,"在线主机个数: %d",_load_bal._online.size());
                LOG(INFO,"离线主机个数: %d",_load_bal._offline.size());
                // 里面重载了bool()，需要布尔值的上下文中被隐式转换为bool类型
                if(auto res = client.Post("/Compiler_Run", compile_string, "application/json;charset=utf-8"))
                {
                    // 5.编译出的结果赋给out_json————>对应CR模块最后的st_content要返回给用户
                    if (res->status == 200) // 200才能访问成功，其他的只是请求成功,但不是2开头就不会访问成功
                    {

                        *out_json = res->body;
                        ma->Decress_load();
                        LOG(INFO,"请求CR模块成功");
                        _load_bal.ShowMachines();
                        break;
                        
                    }
                    ma->Decress_load();
                }
                else
                {
                    LOG(ERROR, "请求主机的id: %d 请求CR模块失败,ip: %s port: %d", id, ma->_ip.c_str(), ma->_port);
                    _load_bal.OffLine(id);
                    _load_bal.ShowMachines();//用来调试的
                }
            }
        }
    };
};